#include "../PreCompile.hpp"
#include "../PkgConfig.hpp"
#include "../CLangModule.hpp"

#include "GCCCompilerBackend.hpp"
#include "GCCOutputParser.hpp"

namespace BuildNG
{
  ///////////
  // GCCCompilerBackend
  Glib::RefPtr<GCCCompilerBackend> GCCCompilerBackend::create()
  {
    return Glib::RefPtr<GCCCompilerBackend>(new GCCCompilerBackend);
  }

  bool GCCCompilerBackend::build_source_file(
    BuildContext&                 context,
    const Glib::ustring&          source_file_name,
    Glib::ustring&                obj_file_name,
    Glib::RefPtr<ErrorListener>   error_listener)
  {
    Glib::ustring abs_gcc_path = get_compiler_path();
    Glib::RefPtr<EGlib::Process> proc = EGlib::Process::create(abs_gcc_path);
    proc->set_working_directory(context.get_cur_dir());
    
    proc->add_argument("-c");
    proc->add_argument(source_file_name);
    
    obj_file_name = source_file_name;
    // TODO: there's a risk for clashes here. Fix that, using proper escaping.
    for(Glib::ustring::iterator it = obj_file_name.begin();it != obj_file_name.end();++it)
    {
      if(*it == '\\' || *it == '/' || *it == '.')
      {
        Glib::ustring::iterator end = it;
        end++;
        obj_file_name.replace(it,end,"_");
      }
    }
    obj_file_name += ".o";
        
    Glib::ustring obj_file_path = Glib::build_filename(context.get_cur_tmp_dir(),obj_file_name);
    
    proc->add_argument("-o" + obj_file_path);
    
    // add the references arguments
    if(cflags_pkg_args != "")
      proc->add_arguments(Glib::shell_parse_argv(cflags_pkg_args));

    Glib::RefPtr<ErrorOutputHandler> error_handler(new ErrorOutputHandler);
    error_handler->error_listener = error_listener;
    proc->set_stderr_handler(error_handler);

    int ret_status = proc->spawn_sync();

    error_handler->flush();
    
    return ret_status == 0;
  }

  bool GCCCompilerBackend::link(
    BuildContext&                 context,
    const std::vector<Glib::ustring> &object_files,
    Glib::RefPtr<ErrorListener>   error_listener)
  {
    Glib::ustring abs_ld_path = get_compiler_path();
    Glib::RefPtr<EGlib::Process> proc = EGlib::Process::create(abs_ld_path);
    proc->set_working_directory(context.get_cur_tmp_dir());

    for(int i = 0;i < (int)object_files.size();i++)
    {
      proc->add_argument(object_files[i]);
    }

    return proc->spawn_sync() == 0;
  }

  Glib::ustring GCCCompilerBackend::get_compiler_path() const
  {
#ifdef G_OS_WIN32
    Glib::ustring ret = "c:/MinGW/bin/gcc.exe";
#else
    Glib::ustring ret = Glib::find_program_in_path("gcc");
#endif
    if(ret == "")
    {
      assert(!"not implemented yet");
//      throw Glib::Exception("gcc Executable not found.");
    }
    return ret;
  }
  
  void GCCCompilerBackend::add_reference(Glib::RefPtr<CLangReference> reference)
  {
    switch(reference->reference_type)
    {
    case CLangReference::ReferenceType_Package:
      {
        Glib::RefPtr<PkgConfig> pkg_config = PkgConfig::get_pkg_config(reference->name);
        cflags_pkg_args += pkg_config->get_cflags() + " ";
        libs_pkg_args += pkg_config->get_lib_flags() + " ";
      }
      break;
      
    case CLangReference::ReferenceType_Module:
      assert(!"not implemented yet");
      break;
      
    default:
      assert(!"Unknown reference type");
    }
  }

  //////////
  // GCCCompilerBackend::ErrorOutputHandler
  void GCCCompilerBackend::ErrorOutputHandler::handle_output(char *buffer,int buffer_size)
  {
    cur_line.reserve(cur_line.size() + buffer_size);
    for(int i = 0;i < buffer_size;i++)
    {
      if(buffer[i] == '\r' || buffer[i] == '\n')
      {
        flush();
      }
      else
      {
        cur_line += buffer[i];
      }
    }
  }

  void GCCCompilerBackend::ErrorOutputHandler::flush()
  {
    GccOutputLine output_line;
    GccOutputLine::parse_gcc_output_line(cur_line,output_line);
    switch(output_line.line_type)
    {
    case GccOutputLine::Error:
      error_listener->report_error(output_line.error_location,output_line.msg);
      break;
    case GccOutputLine::Warning:
      error_listener->report_warning(output_line.error_location,output_line.msg);
      break;
    }

    cur_line.clear();
  }
}
